In software, control flow (or flow of control) describes how execution progresses from one command to the next. In many contexts, such as machine code and an imperative programming language, control progresses sequentially (to the command located immediately after the currently executing command) except when a command transfers control to another point in which case the command is classified as a control flow command. Depending on context, other terms are used instead of command. For example, in machine code, the typical term is instruction and in an imperative language, the typical term is statement.
Although an imperative language encodes control flow explicitly, languages of other programming paradigms are less focused on control flow. A declarative language specifies desired results without prescribing an order of operations. A functional language uses both language constructs and functions to control flow even though they are usually not called control flow statements.
For a central processing unit instruction set, a control flow instruction often alters the program counter and is either an unconditional branch (a.k.a. jump) or a conditional branch. An alternative approach is predication which conditionally enables instructions instead of branching.
An asynchronous control flow transfer such as an interrupt or a signal alters the normal flow of control to a handler before returning control to where it was interrupted.
One way to attack software is to redirect the flow of execution. A variety of control-flow integrity techniques, including Stack canary, buffer overflow protection, shadow stacks, and vtable pointer verification, are used to defend against these attacks.
Some languages limit a label to a number which is sometimes called a line number, although that implies the inherent index of the line, not a label. None-the-less, such numeric labels are typically required to increment from top to bottom in a file even if not be sequential. For example, in BASIC:
In many languages, a label is an alphanumeric identifier, usually appearing at the start of a line and immediately followed by a colon. For example, the following C code defines a label on line 3 which identifies a jump target point at the first statement that follows it (line 4).
if (ok) {
goto success;
}
return;
success:
printf("OK");
}
printf("All done");
} else {
printf("Still workin' on it");
}
void bar() {
printf("Hi");
}
foo();
printf("Done");
}
In a high-level language, this is often provided as a goto statement. Although the keyword may be upper or lower case or one or two words depending on the language, it is like: goto ''label''. When control reaches a goto statement, control then jumps to the statement that follows the indicated label. The goto statement has been considered harmful by many computer scientists, including Dijkstra.
The following Pascal code shows a simple if-then-else. The syntax is similar in Ada:
writeln("yes")
else
writeln("no");
In C:
puts("yes");
} else {
puts("no");
}
In bash:
echo "yes"
else
echo "no"
fi
In Python:
print("yes")
else:
print("no")
In Lisp:
(if (plusp a)
"yes"
"no"))
The following Pascal code shows a relatively simple switch statement. Pascal uses the keyword instead of .
'a': actionOnA;
'x': actionOnX;
'y','z':actionOnYandZ;
else actionOnNoMatch;
end;
In Ada:
when 'a' => actionOnA;
when 'x' => actionOnX;
when 'y' | 'z' => actionOnYandZ;
when others => actionOnNoMatch;
end;
In C:
case 'a':
actionOnA;
break;
case 'x':
actionOnX;
break;
case 'y':
case 'z':
actionOnYandZ;
break;
default:
actionOnNoMatch;
}
In Bash:
a) actionOnA ;;
x) actionOnX ;;
[yz]) actionOnYandZ ;;
*) actionOnNoMatch ;;
esac
In Lisp:
((#\a) action-on-a)
((#\x) action-on-x)
((#\y #\z) action-on-y-and-z)
(else action-on-no-match))
In Fortran:
case ('a')
actionOnA
case ('x')
actionOnX
case ('y','z')
actionOnYandZ
case default
actionOnNoMatch
end select
In a functional programming language, such as Haskell and Scheme, both recursive and iterative processes are expressed with Tail recursion procedures instead of looping constructs that are syntactic.
Example in BASIC:
xxx
NEXT I
Example in Pascal:
xxx
end;
Example in Fortran:
xxx
END DO
In many programming languages, only integers can be used at all or reliably. As a floating-point number is represented imprecisely due to hardware constraints, the following loop might iterate 9 or 10 times, depending on various factors such as rounding error, hardware, compiler. Furthermore, if the increment of X occurs by repeated addition, accumulated rounding errors may mean that the value of X in each iteration can differ quite significantly from the commonly expected sequence of 0.1, 0.2, 0.3, ..., 1.0.
'''for''' X := 0.1 '''step''' 0.1 '''to''' 1.0 '''do'''
Example in Visual Basic:
xxx
LOOP
Example in Pascal:
xxx
until test;
Example in C family of pre-test:
xxx
}
Example in C family of post-test:
xxx
while (test);
Although using the keyword, the three-part C-style loop is a condition-based construct, not a numeric-based one. The second part, the condition, is evaluated before each loop, so the loop is pre-test. The first part is a place to initialize state, and the third part is for incrementing for the next iteration, but both aspects can be performed elsewhere. The following C code implements the logic of a numeric loop that iterates for i from 0 to n-1.
xxx
}
Example in Smalltalk:
Example in Pascal:
Example in Raku:
Example in TCL:
Example in PHP:
Example in Java:
Example in C#:
Example in PowerShell where 'foreach' is an alias of 'ForEach-Object':
Example in Fortran:
Scala has for-expressions, which generalise collection-controlled loops, and also support other uses, such as asynchronous programming. Haskell has do-expressions and comprehensions, which together provide similar function to for-expressions in Scala.
A common case is where the start of the loop is always executed, but the end may be skipped on the last iteration. This was dubbed by Dijkstra a loop which is performed " n and a half times",Edsger W. Dijkstra, personal communication to Donald Knuth on 1974-01-03, cited in and is now called the loop-and-a-half problem. Common cases include reading data in the first part, checking for end of data, and then processing the data in the second part; or processing, checking for end, and then preparing for the next iteration. In these cases, the first part of the loop is executed times, but the second part is only executed times.
This problem has been recognized at least since 1967 by Knuth, with Wirth suggesting solving it via early loop exit. Since the 1990s this has been the most commonly taught solution, using a break statement, as in:
'''loop'''
''statements''
'''if''' ''condition'' '''break'''
''statements''
'''repeat'''
A subtlety of this solution is that the condition is the opposite of a usual while condition: rewriting while condition ... repeat with an exit in the middle requires reversing the condition: loop ... if not condition exit ... repeat. The loop with test in the middle control structure explicitly supports the loop-an-a-half use case, without reversing the condition.
In the following Ada code, the loop exits when X is 0.
Get(X);
if X = 0 then
exit;
end if;
DoSomething(X);
end loop;
A more idiomatic style uses :
Get(X);
exit when X = 0;
DoSomething(X);
end loop;
Python supports conditional execution of code depending on whether a loop was exited early (with a last statement) or not by using an else-clause with the loop. In the following Python code, the break clause is linked to the else statement, and not the inner for statement. Both Python's if and for loops support such an else clause, which is executed only if early exit of the loop has not occurred.
if isprime(n):
print("Set contains a prime number")
break
else:
print("Set did not contain any prime numbers")
The notion of multi-level breaks is of some interest in theoretical computer science, because it gives rise to what is today called the Kosaraju hierarchy. In 1973 S. Rao Kosaraju refined the structured program theorem by proving that it is possible to avoid adding additional variables in structured programming, as long as arbitrary-depth, multi-level breaks from loops are allowed.Kosaraju, S. Rao. "Analysis of structured programs," Proc. Fifth Annual ACM Syrup. Theory of Computing, (May 1973), 240-252; also in J. Computer and System Sciences, 9, 3 (December 1974), cited by . Furthermore, Kosaraju proved that a strict hierarchy of programs exists: for every integer n, there exists a program containing a multi-level break of depth n that cannot be rewritten as a program with multi-level breaks of depth less than n without introducing added variables.
In his 2004 textbook, David Watt uses Tennent's notion of S-algol to explain the similarity between multi-level breaks and return statements. Watt notes that a class of sequencers known as escape sequencers, defined as "sequencer that terminates execution of a textually enclosing command or procedure", encompasses both breaks from loops (including multi-level breaks) and return statements. As commonly implemented, however, return sequencers may also carry a (return) value, whereas the break sequencer as implemented in contemporary languages usually cannot.
'''loop''' '''loop'''
xxx1 read(char);
'''while''' test; '''while''' '''not''' atEndOfFile;
xxx2 write(char);
'''repeat'''; '''repeat''';
The construction here can be thought of as a do loop with the while check in the middle, which allows clear loop-and-a-half logic. Further, by omitting individual components, this single construction can replace several constructions in most programming languages. If xxx1 is omitted, we get a loop with the test at the top (a traditional while loop). If xxx2 is omitted, we get a loop with the test at the bottom, equivalent to a do while loop in many languages. If while is omitted, we get an infinite loop. This construction also allows keeping the same polarity of the condition even when in the middle, unlike early exit, which requires reversing the polarity (adding a not), functioning as until instead of while.
This structure is not widely supported, with most languages instead using if ... break for conditional early exit.
This is supported by some languages, such as Forth, where the syntax is BEGIN ... WHILE ... REPEAT, and the shell script languages Bourne shell (while) and bash, where the syntax is while ... do ... done or until ... do ... done, as:
statement-1
statement-2
...
condition
do
statement-a
statement-b
...
done
The shell syntax works because the while (or until) loop accepts a list of commands as a condition, formally:
'''while''' ''test-commands''; '''do''' ''consequent-commands''; '''done'''
The value (exit status) of the list of test-commands is the value of the last command, and these can be separated by newlines, resulting in the idiomatic form above.
Similar constructions are possible in C and C++ with the comma operator, and other languages with similar constructs, which allow shoehorning a list of statements into the while condition:
statement_a;
statement_b;
}
While legal, this is marginal, and it is primarily used, if at all, only for short modify-then-test cases, as in:
// ...
}
In practical terms, a loop variant is an integer expression which has an initial non-negative value. The variant's value must decrease during each loop iteration but must never become negative during the correct execution of the loop. Loop variants are used to guarantee that loops will terminate.
A loop invariant is an assertion which must be true before the first loop iteration and remain true after each iteration. This implies that when a loop terminates correctly, both the exit condition and the loop invariant are satisfied. Loop invariants are used to monitor specific properties of a loop during successive iterations.
Some programming languages, such as Eiffel contain native support for loop variants and invariants. In other cases, support is an add-on, such as the Java Modeling Language's specification for loop statements in Java.
| Ada | |||||||||||||
| APL | |||||||||||||
| C | |||||||||||||
| C++ | |||||||||||||
| C# | |||||||||||||
| COBOL | |||||||||||||
| Common Lisp | |||||||||||||
| D | |||||||||||||
| Eiffel | |||||||||||||
| F# | |||||||||||||
| FORTRAN 77 | |||||||||||||
| Fortran 90 | |||||||||||||
| Fortran 95 and later | |||||||||||||
| Go | |||||||||||||
| Haskell | |||||||||||||
| Java | |||||||||||||
| JavaScript | |||||||||||||
| Kotlin | |||||||||||||
| Natural | |||||||||||||
| OCaml | |||||||||||||
| Odin | |||||||||||||
| PHP | |||||||||||||
| Perl | |||||||||||||
| Python | |||||||||||||
| QB64 | |||||||||||||
| Rebol | |||||||||||||
| Ruby | |||||||||||||
| Rust | |||||||||||||
| Standard ML | |||||||||||||
| Swift | |||||||||||||
| Visual Basic .NET | |||||||||||||
| PowerShell | |||||||||||||
| Zig |
PL/I has some 22 standard conditions (e.g., ZERODIVIDE SUBSCRIPTRANGE ENDFILE) which can be raised and which can be intercepted by: ON condition action; Programmers can also define and use their own named conditions.
Like the unstructured if, only one statement can be specified so in many cases a GOTO is needed to decide where flow of control should resume.
Unfortunately, some implementations had a substantial overhead in both space and time (especially SUBSCRIPTRANGE), so many programmers tried to avoid using conditions.
A typical example of syntax:
'''ON''' ''condition'' '''GOTO''' ''label''
The following C++ code demonstrates structured exception handling. If an exception propagates from the execution of and the exception object type matches one of the types specified in a catch clause, then that clause is executed. For example, if an exception of type is propagated by , then control jumps from line 2 to 4 and the message "Caught SomeException" is printed and then control jumps to after the statement, line 8. If an exception of any other type is propagated, then control jumps from line 2 to 6. If no exception, then control jumps from 2 to 8.
doSomething();
} catch (const SomeException& e)
std::println("Caught SomeException: {}", e.what());
} catch (...) {
std::println("Unknown error");
}
doNextThing();
Many languages use the C++ keywords (, and ), but some languages use other keywords. For example, Ada uses to introduce an exception handler and instead of . AppleScript incorporates placeholders in the syntax to extract information about the exception as shown in the following AppleScript code.
set myNumber to myNumber / 0
on error e number n from f to t partial result pr
if ( e = "Can't divide by zero" ) then display dialog "You must not do that"
end try
In many languages (including Object Pascal, D, Java, C#, and Python) a clause at the end of a statement is executed at the end of the try statement, whether an exception propagates from the rest of the or not. The following C# code ensures that the stream is closed.
stream = new FileStream("logfile.txt", FileMode.Create);
return ProcessStuff(stream);
}
finally
{
if (stream != null)
{
stream.Close();
}
}
Since this pattern is common, C# provides the statement to ensure cleanup. In the following code, even if ProcessStuff() propagates an exception, the IEEE_EXCEPTIONS object is released. Python's stream statement and Ruby's block argument to with are used to similar effect.
return ProcessStuff(stream);
}
'''exitwhen''' EventA '''or''' EventB '''or''' EventC;
xxx
'''exits'''
EventA: actionA
EventB: actionB
EventC: actionC
'''endexit''';
exitwhen is used to specify the events which may occur within xxx, their occurrence is indicated by using the name of the event as a statement. When some event does occur, the relevant action is carried out, and then control passes just after . This construction provides a very clear separation between determining that some situation applies, and the action to be taken for that situation.
exitwhen is conceptually similar to exception handling, and exceptions or similar constructs are used for this purpose in many languages.
The following simple example involves searching a two-dimensional table for a particular item.
'''exitwhen''' found '''or''' missing;
'''for''' I := 1 '''to''' N '''do'''
'''for''' J := 1 '''to''' M '''do'''
'''if''' table[I,J] = target '''then''' found;
missing;
'''exits'''
found: print ("item is in table");
missing: print ("item is not in table");
'''endexit''';
|
|